<?php

declare (strict_types=1);

namespace magein\pay;

use Endroid\QrCode\ErrorCorrectionLevel;
use Endroid\QrCode\QrCode;
use GuzzleHttp\Exception\GuzzleException;
use magein\pay\data\PayOrderData;
use magein\pay\platform\AliPay;
use magein\pay\platform\WxPay;
use magein\utils\Result;
use Overtrue\Socialite\Exceptions\AuthorizeFailedException;

class Payment
{
    /**
     * @param array $data
     * @return array
     * @throws PayException
     */
    public function checkRequestParams(array $data): array
    {
        if (empty($data)) {
            PayException::code(400201);
        }

        extract($data);

        if (empty($sign) || !PayFactory::singer()->verify($data)) {
            PayException::code(400203);
        }

        if (empty($channel_id)) {
            PayException::code(400205);
        }

        if (empty($platform_id)) {
            PayException::code(400207);
        }

        if (empty($trade_type)) {
            PayException::code(400209);
        }

        if (empty($total_amount) || !is_numeric($total_amount) || $total_amount <= 0) {
            PayException::code(400211);
        }

        if (empty($order_no)) {
            PayException::code(400213);
        }

        return $data;
    }

    /**
     * @param $data
     * @return Result
     */
    public function unify($data): Result
    {
        try {

            $this->checkRequestParams($data);

            // 选择使用的收款账号
            $pay_account = PayAccount::choose($data);
            $config = $pay_account->getConfig();
            // 获取支付平台
            $platform = PayFactory::platform($data);
            // 实例化配置文件
            $platform->init($config);

            // 实例化订单数据
            $pay_no = PayFactory::order()->getPayNo();
            $pay_order_data = new PayOrderData($data);
            $pay_order_data->setPayNo($pay_no);
            $pay_order_data->setAppid($config['appid']);
            $pay_order_data->setMerchantId(($config['merchant_id'] ?? '') ? $config['merchant_id'] : $config['appid']);
            $pay_order_data->setTradeType($data['trade_type'] ?? '');

            $result = $platform->unify($pay_order_data);
            if ($result->getCode()) {
                return $result;
            }

            // 支付平台统一下单
            return Result::success([
                'platform_id' => $data['platform_id'] ?? '',
                'account' => $pay_account->getAccount(),
                'subject' => $pay_order_data->getSubject(),
                'total_amount' => $pay_order_data->getTotalAmount(),
                'trade_type' => $pay_order_data->getTradeType(),
                'order_no' => $pay_order_data->getOrderNo(),
                'pay_no' => $pay_no,
                'pay_data' => $result->getData(),
            ]);

        } catch (PayException $exception) {
            $result = $exception->toResult();
            return Result::error($result->getMsg(), $result->getCode(), $data);
        }
    }

    /**
     * 验证二维码是否有效
     * @param $pay_no
     * @return bool
     */
    public function qrcodeAvailable($pay_no): bool
    {
        try {
            $result = PayFactory::qrcode()->available($pay_no);
        } catch (PayException $exception) {
            $result = false;
        }

        return $result;
    }

    /**
     * @param array $data
     * @return Result
     */
    public function qrcode(array $data): Result
    {

        $temp_save_path = '';

        try {

            if (empty($data)) {
                PayException::code(400201);
            }

            extract($data);

            if (empty($sign) || !PayFactory::singer()->verify($data)) {
                PayException::code(400203);
            }

            if (empty($channel_id)) {
                PayException::code(400205);
            }

            if (empty($total_amount) || !is_numeric($total_amount) || $total_amount <= 0) {
                PayException::code(400211);
            }

            if (empty($order_no)) {
                PayException::code(400213);
            }

            $pay_no = PayFactory::qrcode()->create($data);

            if ($pay_no) {

                $config = PayConfig::qrcode();
                $url = $config['url'] ?? '';
                $size = $config['size'] ?? 300;
                $save_path = $config['save_path'] ?? '';

                if ($url && $save_path) {

                    $filename = $pay_no . '.png';
                    $dir = date('Y/m/d/');
                    $filepath = $save_path . $dir;
                    if (!is_dir($filepath)) {
                        mkdir($filepath, 0777, true);
                    }

                    $temp_save_path = $filepath . $filename;
                    $url .= '?pay_no=' . $pay_no;
                    $qrcode = new QrCode($url);
                    $qrcode->setSize($size);
                    $qrcode->setWriterByName('png');
                    $qrcode->setEncoding('UTF-8');
                    $qrcode->setErrorCorrectionLevel(ErrorCorrectionLevel::HIGH());
                    $qrcode->setValidateResult(false);
                    $qrcode->writeFile($temp_save_path);

                    if (is_file($temp_save_path)) {
                        $temp_save_path = PayFactory::qrcode()->getTempSavePath($temp_save_path);
                    } else {
                        $temp_save_path = '';
                    }
                }
            }

        } catch (PayException $exception) {
            return $exception->toResult();
        }

        return Result::success(compact('temp_save_path', 'pay_no'));
    }

    /**
     * user-agent
     *
     * 微信 : Mozilla/5.0 (Linux; Android 14; 2201123C Build/UKQ1.230917.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/126.0.6478.122 Mobile Safari/537.36 XWEB/1260059 MMWEBSDK/20240501 MMWEBID/4520 MicroMessenger/8.0.50.2701(0x28003252) WeChat/arm64 Weixin NetType/WIFI Language/zh_CN ABI/arm64
     *
     * 支付宝: Mozilla/5.0 (Linux; Android 14; 2201123C Build/UKQ1.230917.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/105.0.5195.148 MYWeb/0.11.0.240702160459 UWS/3.22.2.9999 UCBS/3.22.2.9999_220000000000 Mobile Safari/537.36 NebulaSDK/1.8.100112 Nebula AlipayDefined(nt:4G,ws:393|0|2.75) AliApp(AP/10.6.18.8000) AlipayClient/10.6.18.8000 Language/zh-Hans useStatusBar/true isConcaveScreen/true Region/CNAriver/1.0.0 DTN/2.0
     *
     * @param $pay_no
     * @return Result
     */
    public function scan($pay_no): Result
    {
        try {

            $qrcode = PayFactory::qrcode();

            $record = $qrcode->getByPayNo($pay_no);

            if (empty($record)) {
                PayException::code(400503);
            }

            $params = $record['params'];

            if (!$qrcode->available($record)) {
                PayException::code(400505, $params);
            }

            // 扫码后标记为失效
            $qrcode->unavailable($pay_no);

            $user_agent = $_SERVER['HTTP_USER_AGENT'] ?? '';
            $wx = strpos($user_agent, 'MicroMessenger');
            if ($wx !== false) {
                $params['platform_id'] = PayConfig::qrcode('wx');
                $params['trade_type'] = WxPay::TRADE_TYPE_JSAPI;
                // 选择账号
                $account = PayAccount::choose($params);
                $open_id = ($params['open_id'] ?? '') ?: PayFactory::wxOauth()->getWxOpenId($params);
                if (empty($open_id)) {
                    $platform = PayFactory::platform($params);
                    return Result::success([
                        'oauth' => $platform->oauth($account->getAccount(), $pay_no)
                    ]);
                }
                $params['open_id'] = $open_id;
            } else {
                $params['platform_id'] = PayConfig::qrcode('ali');
                $params['trade_type'] = AliPay::TRADE_TYPE_WAP;
            }
            unset($params['sign']);
            $params['sign'] = PayFactory::singer()->create($params);
            $result = $this->unify($params);

        } catch (PayException $exception) {
            $result = $exception->toResult();
        }

        return $result;
    }


    /**
     * @param $pay_no
     * @param $data
     * @return string
     */
    public function notify($pay_no, $data): string
    {
        // 获取支付平台
        try {

            $pay_order = PayFactory::order()->getByPayNo($pay_no);
            if (empty($pay_order)) {
                return 'success';
            }

            $platform = PayFactory::platform($pay_order);
            $platform->doInit($pay_order);
            $notify = $platform->handleNotify($data);

            // 更新订单状态
            $result = PayFactory::order()->modify($notify);

            // 通知支付结果
            if ($result) {
                PayFactory::notice()->run($notify, $pay_order);
            }

            // 发送响应
            return $notify->getResponse() ?: '';

        } catch (PayException $exception) {

        }

        return 'success';
    }

    /**
     * @param $pay_no
     * @return string
     */
    public function success($pay_no): string
    {
        $pay_order = null;
        $url = '';
        try {
            $i = 0;
            while (true) {
                if ($i >= 10) {
                    break;
                }
                $pay_order = PayFactory::order()->getByPayNo($pay_no);
                if ($pay_order && $pay_order['result'] != 0) {
                    break;
                }
                $i++;
                sleep(1);
            }
        } catch (PayException $exception) {

        }

        if ($pay_order) {
            $url = (new PayNotice())->getReturnUrl($pay_order);
        }

        return $url;
    }

    public function oauth($code, $account, $pay_no)
    {
        try {

            $record = PayFactory::qrcode()->getByPayNo($pay_no);

            if (empty($record) || empty($record['params'] ?? '')) {
                PayException::code(400503);
            }

        } catch (PayException $exception) {
            return $exception->toResult();
        }

        $params = $record['params'];
        $params['platform_id'] = PayConfig::qrcode('wx');
        $params['trade_type'] = WxPay::TRADE_TYPE_JSAPI;
        $params['account'] = $account;

        try {

            $pay_account = PayAccount::choose($params);
            $config = $pay_account->getConfig();

            $platform = PayFactory::platform($params);
            $user = $platform->officialAccount($config)->oauth->userFromCode($code);
            $params['open_id'] = $user->getId();

            PayFactory::wxOauth()->setWxOpenId($params);
            unset($params['sign']);
            $params['sign'] = PayFactory::singer()->create($params);

            return $this->unify($params);

        } catch (PayException $exception) {
            return $exception->toResult();
        } catch (AuthorizeFailedException $exception) {
            return Result::error('获取微信授权错误', 400309, $params);
        } catch (GuzzleException $exception) {
            return Result::error('微信授权请求错误', 400309, $params);
        }
    }

    /**
     * @param $pay_no
     * @param int $type
     * @return Result
     */
    public function query($pay_no, int $type): Result
    {
        try {

            if (empty($pay_no)) {
                PayException::code(400503);
            }

            $data = PayFactory::order()->getByPayNo($pay_no);
            if (empty($data)) {
                PayException::code(400503);
            }

            $platform_id = $data['platform_id'] ?? '';

            // 获取支付平台
            $platform_pay_result = function () use ($pay_no, $data) {
                $platform = PayFactory::platform($data);
                $platform->doInit($data);
                return $platform->queryByPayNo($pay_no);
            };

            $return['platform_id'] = $platform_id;

            if ($type == 1) {
                $return['pay_res'] = $data;
            } elseif ($type == 2) {
                $return['platform_res'] = $platform_pay_result();
            } else {
                $return['pay_res'] = $data;
                $return['platform_res'] = $platform_pay_result();
            }

            $result = Result::success($return);

        } catch (PayException $payException) {
            $result = $payException->toResult();
        }

        return $result;
    }
}